package sync;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;

public class FileList {

	private final boolean DEBUG = false;

	private File curDir;
	private ArrayList<FileInfo> dirList;
	private boolean setFileAttributeHidden = false; // Is true if we are in Windows and we include hidden files
	boolean useExactTime = false; // Makes exact comparisons on modification time

	public FileList(boolean useExactTime) {
		dirList = new ArrayList<FileInfo>();
		this.useExactTime = useExactTime;
	}

	public FileList(String path, boolean useExactTime) throws FileNotFoundException, Exception {
		dirList = new ArrayList<FileInfo>();
		fromTextFileToArrayList(path);
		this.useExactTime = useExactTime;
	}

	public FileList(File curDir, boolean includeHidden, boolean useExactTime) throws Exception {
		if(DEBUG) System.out.println("Starting to create filelist from " + curDir.getAbsolutePath());
		this.curDir = curDir;
		this.useExactTime = useExactTime;

		// Check if we have the special case where we need to take care of hidden files in a Windows system
		if(includeHidden) {
			String os = System.getProperty("os.name").toUpperCase();
			if (os.indexOf("WIN") >= 0) {
				setFileAttributeHidden = true;
			}
		}

		dirList = new ArrayList<FileInfo>();
		copyToArrayList(curDir, "", includeHidden);
		Collections.sort(dirList); // Do not know if the sort works correctly!!!!!
	}

	public ArrayList<FileInfo> getDirList() {
		return dirList;
	}

	public File getCurDir() {
		return curDir;
	}

	public int getSize() {
		return dirList.size();
	}

	public FileInfo get(int index) {
		return (FileInfo)dirList.get(index);
	}

	public void addFileInfo(FileInfo fi) {
		dirList.add(fi);
	}

	// Will crash if index does not exist
	public void removeFileInfo(int index) {
		dirList.remove(index);
	}

	public boolean getUseExactTime() {
		return useExactTime;
	}

	private void fromTextFileToArrayList(String path) throws FileNotFoundException, Exception {

		File readFile = new File(path);
		// Open the file that is the first command line parameter
		FileInputStream fstream = new FileInputStream(readFile);
		// Convert our input stream to a BufferedReader
		BufferedReader in = new BufferedReader(new InputStreamReader(fstream, "UTF-8"));

		// Continue to read lines while there are still some left to read
		String tempLine;
		while ( (tempLine = in.readLine()) != null) {

			String[] tempSplit = new String[4];
			tempSplit = tempLine.split("\\\\\\/");
			//System.out.println("FL||" + path + "||" + tempSplit[0] + "||" + tempSplit[1] + "||" + tempSplit[2] + "||" + tempSplit[3]); //TODO use other split symbol instead of ":"

			long modTime = Long.valueOf(tempSplit[1]).longValue();
			long fileLength = Long.valueOf(tempSplit[2]).longValue();
			int windowsHidden = Integer.valueOf(tempSplit[3]);

			dirList.add(new FileInfo(tempSplit[0], modTime, fileLength, windowsHidden));
		}

		in.close();
		fstream.close();
	}

	// Returns 1 if you are running under a windows system and the file is hidden
	// Otherwise returns 0
	private int checkIfWindowsHidden(File tempFile) {
		if (setFileAttributeHidden && tempFile.isHidden()) {
			return 1;
		}
		else {
			return 0;
		}
	}

	// Gets an array of Files from the directory and saves it into an ArrayList
	// Handles underlying directory's
	// Deletes all files that ends with .partSync. Which are files that have not completely been synced earlier
	private void copyToArrayList(File curDir, String path, boolean includeHidden) throws Exception {

		if(DEBUG) System.out.println("Itterate copyToArrayList()");

		File[] tempList = curDir.listFiles();
		int nof = tempList.length;  

		for(int i=0; i<nof; i++) {
			if( !tempList[i].isHidden() || includeHidden ) {

				int isWindowsSystem = checkIfWindowsHidden(tempList[i]);
				String fileName = tempList[i].getName();
				long lastMod = tempList[i].lastModified();
				long fileLength = 0; // Zero for directory's

				if(tempList[i].isDirectory()) { // And directory to fileList and recourse into directory
					dirList.add(new FileInfo(path + fileName + "/", lastMod, fileLength, isWindowsSystem));
					copyToArrayList(new File(curDir, fileName), path + fileName + "/", includeHidden);
				}
				else {
					if(fileName.endsWith(".partSync")) { // Delete file if it ends with .partSync
						File fileToBeDelete = new File(curDir.getAbsolutePath() + File.separator + fileName);
						fileToBeDelete.delete();
					}
					else { // Add file to fileList
						fileLength = tempList[i].length();
						dirList.add(new FileInfo(path + fileName, lastMod, fileLength, isWindowsSystem));
					}
				}

			}
		}
	}

	// FileName = path and name of file to save
	// Saves files as filename|timestamp|filelength|windowsHidden for each file
	public void saveToTextFile(String fileName) throws FileNotFoundException, Exception {

		PrintWriter pw = new PrintWriter(fileName, "UTF-8"); // declare a print writer object

		for(int i=0; i < dirList.size(); i++) {
			FileInfo tempFile = (FileInfo) dirList.get(i);
			pw.println(tempFile.toString());
		}

		pw.close();
	}

	// Finds out witch files to send to the remote computer 
	// by comparing two FileLists and returning a FileList 
	// witch contains the file names and modTime to be sent.
	// If forceMode is true then all files that differ in modTime
	// will be sent.
	//
	// this will ignore upper or lower case file names
	public FileList compareFileList(FileList fileList2, boolean forceMode) {

		// Initiating variables
		FileList filesToSend = new FileList(useExactTime);
		int listSize = getSize();
		int listSize2 = fileList2.getSize();
		int pos1 = 0;
		int pos2 = 0;
		FileInfo temp1, temp2;
		ArrayList<FileInfo> dirList2 = new ArrayList<FileInfo>();
		dirList2 = fileList2.getDirList();

		// Main loop
		if(listSize2 > 0) { // If remoteFileList is empty 
			while(pos1 < listSize) {

				temp1 = (FileInfo) dirList.get(pos1);
				temp2 = (FileInfo) dirList2.get(pos2);
				int comp = temp1.compareTo(temp2);

				if(comp > 0) { // Does not add a file to the list to be sent
					if (pos2 < (listSize2-1)) {
						pos2++;
					} else {
						filesToSend.addFileInfo(temp1);
						pos1++;
					}
				}
				else if(comp < 0) { // Adds a file to the list to be sent
					filesToSend.addFileInfo(temp1);
					pos1++;
				}
				else {
					if(!forceMode) { // Adds a file to the list IF its more upp to date
						if(temp1.compareModTime(temp2.getModTime(), useExactTime) > 0) {
							filesToSend.addFileInfo(temp1);
						}
					}
					else { // Adds a file to the list IF modtime differs. Used in backup mode
						if(temp1.compareModTime(temp2.getModTime(), useExactTime) != 0) {
							filesToSend.addFileInfo(temp1);
						}
					}
					pos1++;
					if (pos2 < (listSize2-1)) {
						pos2++;
					}
				}
			}
		}
		else {
			return this;
		}
		return filesToSend;
	}



	// Returns a FileList of elements that this FileList contains but that does not exist in fileList2
	public FileList compareFileListNamesOnly(FileList fileList2) {

		// Initiating variables
		FileList filesToSend = new FileList(useExactTime);
		int listSize = getSize();
		int listSize2 = fileList2.getSize();
		int pos1 = 0;
		int pos2 = 0;
		FileInfo temp1, temp2;
		ArrayList<FileInfo> dirList2 = fileList2.getDirList();

		// Main loop
		if(listSize2 > 0) { // If remoteFileList is empty 
			while(pos1 < listSize) {

				temp1 = (FileInfo) dirList.get(pos1);
				temp2 = (FileInfo) dirList2.get(pos2);
				int comp = temp1.compareTo(temp2);

				if(comp > 0) { // Does not add a file to the list to be sent
					if (pos2 < (listSize2-1)) {
						pos2++;
					} else {
						filesToSend.addFileInfo(temp1);
						pos1++;
					}
				}
				else if(comp < 0) { // Adds a file to the list to be sent
					filesToSend.addFileInfo(temp1);
					pos1++;
				}
				else { 
					pos1++;
					if (pos2 < (listSize2-1)) {
						pos2++;
					}
				}
			}
		}
		else {
			return this;
		}
		return filesToSend;
	}

	// Works like compareFileListNamesOnly(FileList fileList2) and can replace it
	public FileList removeEqualFileInfos(FileList fileList2) {

		// Initiating variables
		int listSize1 = getSize();
		int listSize2 = fileList2.getSize();

		if(listSize2 == 0) {
			return this;
		}

		// Main loop
		else {
			// Initiating variables
			FileList returnFL = new FileList(useExactTime);
			int pos1 = 0;
			int pos2 = 0;
			FileInfo temp1, temp2;
			ArrayList<FileInfo> dirList2 = fileList2.getDirList();

			while(pos1 < listSize1) {

				temp1 = (FileInfo) dirList.get(pos1);
				temp2 = (FileInfo) dirList2.get(pos2);
				int comp = temp1.compareTo(temp2);

				if(comp > 0) { // temp1 is higher alphabetically than temp2
					++pos2;
				}
				else if(comp < 0) { // temp1 is lower alphabetically than temp2
					returnFL.addFileInfo(temp1);
					++pos1;
				}
				else { // temp1 and temp2 has the same fileName
					++pos1;
					++pos2;
				}

				// If we reach the end of one FileList
				if(pos2 >= listSize2) {
					while(pos1 < listSize1) {
						temp1 = (FileInfo) dirList.get(pos1);
						returnFL.addFileInfo(temp1);
						++pos1;
					}
				}
			}

			return returnFL;	
		}
	}

	// If 'this' contains fileInfos that fileList2 does not have it returns a FileList without this fileInfos
	// otherwise it returns the same FileList, 'this'
	public FileList removeFileInfosThatDoesNotExistIn(FileList fileList2) {

		// Initiating variables
		int listSize1 = getSize();
		int listSize2 = fileList2.getSize();

		if(listSize2 == 0) {
			return fileList2;
		}

		// Main loop
		else {
			// Initiating variables
			FileList returnFL = new FileList(useExactTime);
			int pos1 = 0;
			int pos2 = 0;
			FileInfo temp1, temp2;
			ArrayList<FileInfo> dirList2 = fileList2.getDirList();

			while(pos1 < listSize1) {

				temp1 = (FileInfo) dirList.get(pos1);
				temp2 = (FileInfo) dirList2.get(pos2);
				int comp = temp1.compareTo(temp2);

				if(comp > 0) { // temp1 is higher alphabetically than temp2
					++pos2;
				}
				else if(comp < 0) { // temp1 is lower alphabetically than temp2
					++pos1;
				}
				else { // temp1 and temp2 has the same fileName
					returnFL.addFileInfo(temp1);
					++pos1;
					++pos2;
				}

				// If we reach the end of the second FileList
				if(pos2 >= listSize2) {
					pos1 = listSize1;
				}
			}

			return returnFL;	
		}
	}

	// Merges two FileLists in a sorted way
	// If fileName exists on both fileLists save the one with later modTime if forceMode if false
	// otherwise save the one from fileList2, if forceMode is true
	public FileList mergeFileLists(FileList fileList2, boolean forceMode) {

		// Initiating variables
		int listSize1 = getSize();
		int listSize2 = fileList2.getSize();

		if(listSize1 == 0) {
			return fileList2;
		}
		else if(listSize2 == 0) {
			return this;
		}

		// Main loop
		else {
			// Initiating variables
			FileList returnFL = new FileList(useExactTime);
			int pos1 = 0;
			int pos2 = 0;
			FileInfo temp1, temp2;
			ArrayList<FileInfo> dirList2 = fileList2.getDirList();

			while(pos1 < listSize1) {

				temp1 = (FileInfo) dirList.get(pos1);
				temp2 = (FileInfo) dirList2.get(pos2);
				int comp = temp1.compareTo(temp2);

				if(comp > 0) { // temp1 is higher alphabetically than temp2
					returnFL.addFileInfo(temp2);
					++pos2;
				}
				else if(comp < 0) { // temp1 is lower alphabetically than temp2
					returnFL.addFileInfo(temp1);
					++pos1;
				}
				else { // temp1 and temp2 has the same fileName
					if(!forceMode) { // save the one with later modTime
						if(temp1.compareModTime(temp2.getModTime(), useExactTime) > 0) {
							returnFL.addFileInfo(temp1);
						}
						else {
							returnFL.addFileInfo(temp2);
						}
					}
					else { // save the one from fileList2
						returnFL.addFileInfo(temp2);
					}
					++pos1;
					++pos2;
				}

				// If we reach the end of the second FileList
				if(pos1 >= listSize1) {
					while(pos2 < listSize2) {
						temp2 = (FileInfo) dirList2.get(pos2);
						returnFL.addFileInfo(temp2);
						++pos2;
					}
				}
				// If we reach the end of the first FileList
				else if(pos2 >= listSize2) {
					while(pos1 < listSize1) {
						temp1 = (FileInfo) dirList.get(pos1);
						returnFL.addFileInfo(temp1);
						++pos1;
					}
				}
			}

			return returnFL;	
		}
	}

	// This will ignore upper and lower case
	public boolean equals(FileList fileList2) {

		boolean equal = true;

		if(dirList.size() != fileList2.getDirList().size()) {
			equal = false;
		}
		else {
			for(int i = 0; i < dirList.size(); ++i) {

				// Check file names
				String name1 = ((FileInfo)dirList.get(i)).getName();
				String name2 = ((FileInfo)fileList2.getDirList().get(i)).getName();
				if( !name1.equalsIgnoreCase(name2) ) { 
					equal = false;
				}

				// Check modtime on files
				long modTime2 = ((FileInfo)fileList2.getDirList().get(i)).getModTime();
				if(dirList.get(i).compareModTime(modTime2, useExactTime) != 0L) {
					equal = false;
				}
			}
		}

		return equal;
	}

	public boolean isEmpty() {

		if(dirList.size() == 0) {
			return true;
		}
		else {
			return false;
		}
	}

}
